/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is confidential property of Nordic
 * Semiconductor ASA.Terms and conditions of usage are described in detail
 * in NORDIC SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include <stdbool.h>
#include <stdint.h>
#include "nrf.h"
#include "twi_master.h"
#include "twi_master_config.h"
#include "nrf_delay.h"
#include "nrf_gpio.h"

/* Max cycles approximately to wait on RXDREADY and TXDREADY event, this is optimum way instead of using timers, this is not power aware, negetive side is this is not power aware */
#define MAX_TIMEOUT_LOOPS             (10000UL)        /*!< MAX while loops to wait for RXD/TXD event */

static bool twi_master_write(uint8_t *data, uint8_t data_length, bool issue_stop_condition)
{
    uint32_t timeout = MAX_TIMEOUT_LOOPS;   /* max loops to wait for EVENTS_TXDSENT event*/

    if(data_length == 0)
    {
        /* gently return false for requesting data of size 0 */
        return false;
    }

    NRF_TWI1->TXD = *data++;
    NRF_TWI1->TASKS_STARTTX = 1;
    while(true)
    {
        while(NRF_TWI1->EVENTS_TXDSENT == 0 && (--timeout))
        {
        }

        if(timeout == 0)
        {
            /* timeout before receiving event*/
            return false;
        }

        NRF_TWI1->EVENTS_TXDSENT = 0;
        if (--data_length == 0)
            break;

        NRF_TWI1->TXD = *data++;
    }
    if (issue_stop_condition)
    {
        NRF_TWI1->TASKS_STOP = 1;
    }

    /* wait until stop sequence is sent and clear the EVENTS_STOPPED */
    while(NRF_TWI1->EVENTS_STOPPED == 0)
    {
    }
    NRF_TWI1->EVENTS_STOPPED = 0;

    return true;
}

static bool twi_master_read(uint8_t *data, uint8_t data_length, bool issue_stop_condition)
{
    uint32_t timeout = MAX_TIMEOUT_LOOPS;   /* max loops to wait for RXDREADY event*/

    if(data_length == 0)
    {
        /* gently return false for requesting data of size 0 */
        return false;
    }


    if (data_length == 1)
    {
        NRF_PPI->CH0_TEP = (uint32_t)&NRF_TWI1->TASKS_STOP;
    }
    else
    {
        NRF_PPI->CH0_TEP = (uint32_t)&NRF_TWI1->TASKS_SUSPEND;
    }
    NRF_PPI->CHENSET = PPI_CHENSET_CH0_Msk;
    NRF_TWI1->TASKS_STARTRX = 1;
    while(true)
    {
        while((NRF_TWI1->EVENTS_RXDREADY == 0) && (--timeout))
        {
        }

        if(timeout == 0)
        {
            /* timeout before receiving event*/
            return false;
        }

        NRF_TWI1->EVENTS_RXDREADY = 0;
        *data++ = NRF_TWI1->RXD;

        /* configure PPI to stop TWI master before we get last BB event */
        if (--data_length == 1)
        {
            NRF_PPI->CH0_TEP = (uint32_t)&NRF_TWI1->TASKS_STOP;
        }

        if (data_length == 0)
            break;

        NRF_TWI1->TASKS_RESUME = 1;
    }

    /* wait until stop sequence is sent and clear the EVENTS_STOPPED */
    while(NRF_TWI1->EVENTS_STOPPED == 0)
    {
    }
    NRF_TWI1->EVENTS_STOPPED = 0;

    NRF_PPI->CHENCLR = PPI_CHENCLR_CH0_Msk;
    return true;
}

/**
 * Detects stuck slaves (SDA = 0 and SCL = 1) and tries to clear the bus.
 *
 * @return
 * @retval false Bus is stuck.
 * @retval true Bus is clear.
 */
static bool twi_master_clear_bus(void)
{
    bool bus_clear;

    TWI_SDA_HIGH();
    TWI_SCL_HIGH();
    TWI_DELAY();

    if (TWI_SDA_READ() == 1 && TWI_SCL_READ() == 1)
    {
        bus_clear = true;
    }
    else
    {
        bus_clear = false;

        // Clock max 18 pulses worst case scenario(9 for master to send the rest of command and 9 for slave to respond) to SCL line and wait for SDA come high
        for (uint_fast8_t i=18; i--;)
        {
            TWI_SCL_LOW();
            TWI_DELAY();
            TWI_SCL_HIGH();
            TWI_DELAY();

            if (TWI_SDA_READ() == 1)
            {
                bus_clear = true;
                break;
            }
        }
    }

    return bus_clear;
}

bool twi_master_init(void)
{
    /* The two wire interface 1 (TWI1) master shares the SPI1 base address.
     power the device, wait for it until it is powered up and enable interrupts */
    NRF_POWER->PERPOWER |= POWER_PERPOWER_SPI1_TWI1_Msk | POWER_PERPOWER_PPI_Msk;
    while ((NRF_POWER->PERRDY & POWER_PERRDY_SPI1_TWI1_Msk) == 0)
    {
    }
    while ((NRF_POWER->PERRDY & POWER_PERRDY_PPI_Msk) == 0)
    {
    }
    
    /* To secure correct signal levels on the pins used by the TWI
       master when the system is in OFF mode, and when the TWI master is 
       disabled, these pins must be configured in the GPIO peripheral.
    */
    NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_CLOCK_PIN_NUMBER] = 
        (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
      | (GPIO_PIN_CNF_DRIVE_S0D1     << GPIO_PIN_CNF_DRIVE_Pos)
      | (GPIO_PIN_CNF_PULL_Pullup    << GPIO_PIN_CNF_PULL_Pos)
      | (GPIO_PIN_CNF_INPUT_Connect  << GPIO_PIN_CNF_INPUT_Pos)
      | (GPIO_PIN_CNF_DIR_Input      << GPIO_PIN_CNF_DIR_Pos);    

    NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_DATA_PIN_NUMBER] = 
        (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
      | (GPIO_PIN_CNF_DRIVE_S0D1     << GPIO_PIN_CNF_DRIVE_Pos)
      | (GPIO_PIN_CNF_PULL_Pullup    << GPIO_PIN_CNF_PULL_Pos)
      | (GPIO_PIN_CNF_INPUT_Connect  << GPIO_PIN_CNF_INPUT_Pos)
      | (GPIO_PIN_CNF_DIR_Input      << GPIO_PIN_CNF_DIR_Pos);    

    NRF_TWI1->EVENTS_RXDREADY = 0;
    NRF_TWI1->EVENTS_TXDSENT = 0;
    NRF_TWI1->PSELSCL = TWI_MASTER_CONFIG_CLOCK_PIN_NUMBER;
    NRF_TWI1->PSELSDA = TWI_MASTER_CONFIG_DATA_PIN_NUMBER;
    NRF_TWI1->FREQUENCY = TWI_FREQUENCY_FREQUENCY_K100 << TWI_FREQUENCY_FREQUENCY_Pos;
    NRF_PPI->CH0_EEP = (uint32_t)&NRF_TWI1->EVENTS_BB;
    NRF_PPI->CH0_TEP = (uint32_t)&NRF_TWI1->TASKS_SUSPEND;
    NRF_PPI->CHENCLR = PPI_CHENCLR_CH0_Msk;
    NRF_TWI1->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos;

    return twi_master_clear_bus();
}

bool twi_master_transfer(uint8_t address, uint8_t *data, uint8_t data_length, bool issue_stop_condition)
{
    bool transfer_succeeded = true;
    if (data_length > 0 && twi_master_clear_bus())
    {
        NRF_TWI1->ADDRESS = (address >> 1);

        if ((address & TWI_READ_BIT) != 0)
        {
            transfer_succeeded = twi_master_read(data, data_length, issue_stop_condition);
        }
        else
        {
            transfer_succeeded = twi_master_write(data, data_length, issue_stop_condition);
        }
    }
    return transfer_succeeded;
}

/*lint --flb "Leave library region" */
